﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.ComponentModel;

namespace Airport
{
    [Serializable]
    [DataContract]
    public class Edge : IEquatable<Edge>/*, INotifyPropertyChanged*/
    {
        // TODO Set this in airport map
        // Width offset, this should be set elsewhere
        private const double _widthOffset = 7;
        private const bool _debugMessages = false;

        private Node _endpoint1;
        private Node _endpoint2;
        private double _length;
        private double _cost;
        private string _name;
        private double _averageSpeed;
        private Dictionary<Node, List<Edge>> _neighboringEdges;
        private Dictionary<Node, List<Edge>> _accessibleNeighboringEdges;
        private NodeEqualityComparer _nodeEqualityComparer;
        private string _holdingShortOfSurfaceName;

        [NonSerialized]
        private EventHandler<NodeChangedEventArgs> _endpoint1Changed;

        public event EventHandler<NodeChangedEventArgs> Endpoint1Changed
        {
            add
            {
                _endpoint1Changed += value;
            }

            remove
            {
                _endpoint1Changed -= value;
            }
        }
        
        [NonSerialized]
        private EventHandler<NodeChangedEventArgs> _endpoint2Changed;

        public event EventHandler<NodeChangedEventArgs> Endpoint2Changed
        {
            add
            {
                _endpoint2Changed += value;
            }

            remove
            {
                _endpoint2Changed -= value;
            }
        }

        public Edge()
        {
            _endpoint1 = null;
            _endpoint2 = null;
            _length = float.MaxValue;
            _cost = float.MaxValue;
            _nodeEqualityComparer = new NodeEqualityComparer();
            _neighboringEdges = new Dictionary<Node, List<Edge>>(_nodeEqualityComparer);
            
            _accessibleNeighboringEdges = new Dictionary<Node, List<Edge>>(_nodeEqualityComparer);
            _holdingShortOfSurfaceName = "";
        }

        public Edge(Node endpoint1, Node endpoint2)
        {
            _endpoint1 = endpoint1;
            _endpoint2 = endpoint2;
            
            if (endpoint1 != null && endpoint2 != null)
            {
                _length = endpoint1.DistanceTo(endpoint2);
            }
            else
            {
                _length = double.MaxValue;
            }
            _nodeEqualityComparer = new NodeEqualityComparer();
            _neighboringEdges = new Dictionary<Node, List<Edge>>(_nodeEqualityComparer);
            _neighboringEdges.Add(_endpoint1, new List<Edge>());
            _neighboringEdges.Add(_endpoint2, new List<Edge>());

            _accessibleNeighboringEdges = new Dictionary<Node, List<Edge>>(_nodeEqualityComparer);
            _accessibleNeighboringEdges.Add(_endpoint1, new List<Edge>());
            _accessibleNeighboringEdges.Add(_endpoint2, new List<Edge>());
            _holdingShortOfSurfaceName = "";
        }

        [DataMember]
        public Node Endpoint1
        {
            get
            {
                return _endpoint1;
            }

            set
            {
                int oldX = -1;
                int oldY = -1;
                if (_endpoint1 != null)
                {
                    oldX = _endpoint1.XPos;
                    oldY = _endpoint1.YPos;
                    // Uncomment when changing endpoints in AirportConfigurationEditor
                    //EndpointUpdate(_endpoint1, value);
                }
                _endpoint1 = value;
                // Uncomment when changing endpoints in AirportConfigurationEditor
                //_endpoint1Changed(this, new NodeChangedEventArgs(oldX, oldY));
            }
        }

        [DataMember]
        public Node Endpoint2
        {
            get
            {
                return _endpoint2;
            }

            set
            {
                int oldX = -1;
                int oldY = -1;
                if (_endpoint2 != null)
                {
                    oldX = _endpoint2.XPos;
                    oldY = _endpoint2.YPos;
                    
                    // Uncomment when changing endpoints in AirportConfigurationEditor
                    //EndpointUpdate(_endpoint2, value);
                }
                _endpoint2 = value;
                // Uncomment when changing endpoints in AirportConfigurationEditor
                //_endpoint2Changed(this, new NodeChangedEventArgs(oldX, oldY));
            }
        }

        public void EndpointUpdate(Node oldEndpoint, Node updatedEndpoint)
        {
            // Reassign previous neighbors of Endpoint to the new Endpoint
            List<Edge> oldEndpointNeighbors = _neighboringEdges[oldEndpoint];
            List<Edge> oldEndpointAccessibleNeighbors = _accessibleNeighboringEdges[oldEndpoint];
            _neighboringEdges.Remove(oldEndpoint);
            _accessibleNeighboringEdges.Remove(oldEndpoint);

            // Change neighbors' shared endpoint to the new value
            foreach (Edge neighbor in oldEndpointNeighbors)
            {
                List<Edge> sharedEndpointNeighbors = new List<Edge>();
                if (neighbor._endpoint1 == oldEndpoint)
                {
                    sharedEndpointNeighbors = neighbor.NeighboringEdges[neighbor._endpoint1];
                    neighbor.NeighboringEdges.Remove(neighbor._endpoint1);
                }
                else if (neighbor._endpoint2 == oldEndpoint)
                {
                    sharedEndpointNeighbors = neighbor.NeighboringEdges[neighbor._endpoint2];
                    neighbor.NeighboringEdges.Remove(neighbor._endpoint2);
                }
                neighbor.NeighboringEdges[updatedEndpoint] = sharedEndpointNeighbors;
            }
            _neighboringEdges[updatedEndpoint] = oldEndpointNeighbors;

            // Change accessible neighbors' shared endpoint to the new value
            foreach (Edge neighbor in oldEndpointAccessibleNeighbors)
            {
                List<Edge> sharedEndpointAccessibleNeighbors = new List<Edge>();
                if (neighbor._endpoint1 == oldEndpoint)
                {
                    sharedEndpointAccessibleNeighbors = neighbor.AccessibleNeighboringEdges[neighbor._endpoint1];
                    neighbor.AccessibleNeighboringEdges.Remove(neighbor._endpoint1);
                }
                else if (neighbor._endpoint2 == oldEndpoint)
                {
                    sharedEndpointAccessibleNeighbors = neighbor.AccessibleNeighboringEdges[neighbor._endpoint2];
                    neighbor.AccessibleNeighboringEdges.Remove(neighbor._endpoint2);
                }
                neighbor.AccessibleNeighboringEdges[updatedEndpoint] = sharedEndpointAccessibleNeighbors;
            }
            _accessibleNeighboringEdges[updatedEndpoint] = oldEndpointAccessibleNeighbors;

            // Update each neighbor's shared endpoint
            foreach (Edge neighbor in oldEndpointNeighbors)
            {
                if (neighbor._endpoint1 == oldEndpoint)
                {
                    neighbor._endpoint1 = updatedEndpoint;
                }
                else if (neighbor._endpoint2 == oldEndpoint)
                {
                    neighbor._endpoint2 = updatedEndpoint;
                }
            }
            // Update each accessible accessibleNeighbor's shared endpoint
            foreach (Edge accessibleNeighbor in oldEndpointAccessibleNeighbors)
            {
                if (accessibleNeighbor._endpoint1 == oldEndpoint)
                {
                    accessibleNeighbor._endpoint1 = updatedEndpoint;
                }
                else if (accessibleNeighbor._endpoint2 == oldEndpoint)
                {
                    accessibleNeighbor._endpoint2 = updatedEndpoint;
                }
            }
        }

        public void OnEndpoint1Changed(object sender, NodeChangedEventArgs e)
        {
            Node updatedEndpoint1 = (Node)sender;
            //EndpointUpdate(new Node(e.OldX, e.OldY), updatedEndpoint1);

            //PropertyChanged(this, new PropertyChangedEventArgs("Endpoint1"));
        }

        public void OnEndpoint2Changed(object sender, NodeChangedEventArgs e)
        {
            Node updatedEndpoint2 = (Node)sender;
            //EndpointUpdate(new Node(e.OldX, e.OldY), updatedEndpoint2);

            //PropertyChanged(this, new PropertyChangedEventArgs("Endpoint2"));
        }

        //public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Straight-line distance between the Edge's endpoints, in pixels.
        /// </summary>
        [DataMember]
        public double Length
        {
            get
            {
                return _length;
            }

            set
            {
                _length = value;
            }
        }

        [DataMember]
        public double Cost
        {
            get
            {
                return _cost;
            }

            set
            {
                _cost = value;
            }
        }

        /// <summary>
        /// The name of the Surface of which this Edge is a part.
        /// </summary>
        [DataMember]
        public string Name
        {
            get
            {
                return _name;
            }

            set
            {
                _name = value;
            }
        }

        /// <summary>
        /// The Average Speed of planes traveling on this edge, in m/s
        /// </summary>
        [DataMember]
        public double AverageSpeed
        {
            get
            {
                return _averageSpeed;
            }

            set
            {
                _averageSpeed = value;
            }
        }

        [DataMember]
        public string HoldingShortOfSurfaceName
        {
            get
            {
                return _holdingShortOfSurfaceName;
            }

            set
            {
                _holdingShortOfSurfaceName = value;
            }
        }

        /// <summary>
        /// The Edges which are addjacent to this Edge.
        /// The key is a Node shared by this Edge and neighboring Edges.
        /// The value is a List of neighboring Edges that also contain the key Node
        /// as an endpoint.
        /// </summary>        
        public Dictionary<Node, List<Edge>> NeighboringEdges
        {
            get
            {
                return _neighboringEdges;
            }

            set
            {
                _neighboringEdges = value;
            }
        }

        /// <summary>
        /// The Edges which can be reached from this Edge.
        /// The key is a Node shared by this Edge and neighboring Edges.
        /// The value is a List of neighboring Edges that also contain the key Node
        /// as an endpoint.
        /// </summary>
        public Dictionary<Node, List<Edge>> AccessibleNeighboringEdges
        {
            get
            {
                return _accessibleNeighboringEdges;
            }

            set
            {
                _accessibleNeighboringEdges = value;
            }
        }

        public NodeEqualityComparer NodeEqualityComparer
        {
            get
            {
                return _nodeEqualityComparer;
            }

            set
            {
                _nodeEqualityComparer = value;
            }
        }

        public Node OtherEndpoint(Node endpoint)
        {
            if (endpoint == null)
            {
                return null;
            }

            if (endpoint.Equals(_endpoint1))
            {
                return _endpoint2;
            }
            
            return _endpoint1;            
        }

        /// <summary>
        /// Determines whether the given WorldVector is within the bounds of the Edge.
        /// </summary>
        /// <param name="positionVector">A WorldVector representing a position.</param>
        /// <returns>True if the given WorldVector is on this Edge, false otherwise.</returns>
        public bool ContainsPositionVector(WorldVector positionVector)
        {
            //double xDifference, yDifference, slope;
            //double endpoint1XDifference, endpoint1YDifference, endpoint2XDifference, endpoint2YDifference;
            bool end1XGreater = positionVector.X > _endpoint1.XPos;
            bool end2XGreater = positionVector.X > _endpoint2.XPos;
            bool end1XLess = positionVector.X < _endpoint1.XPos;
            bool end2XLess = positionVector.X < _endpoint2.XPos;
            bool end1YGreater = positionVector.Y > _endpoint1.YPos;
            bool end2YGreater = positionVector.Y > _endpoint2.YPos;
            bool end1YLess = positionVector.Y < _endpoint1.YPos;
            bool end2YLess = positionVector.Y < _endpoint2.YPos;

            if (positionVector.DistanceToNode(_endpoint1) <= _widthOffset || positionVector.DistanceToNode(_endpoint2) <= _widthOffset)
            {
                Debug.WriteLineIf(_debugMessages, "positionVector is close to an endpoint");
                return true;
            }

            if (end1XGreater && end2XGreater)
            {
                Debug.WriteLineIf(_debugMessages, "end1XGreater && end2XGreater");
                // If positionVector is farther right of this Edge than _widthOffset 
                if (Math.Abs(positionVector.X - _endpoint1.XPos) > _widthOffset && Math.Abs(positionVector.X - _endpoint2.XPos) > _widthOffset)
                {
                    Debug.WriteLineIf(_debugMessages, "positionVector is to the right");
                    return false;
                }
            }

            if (end1XLess && end2XLess)
            {
                Debug.WriteLineIf(_debugMessages, "end1XLess && end2XLess");
                // If positionVector is farther left of this Edge than _widthOffset 
                if (Math.Abs(positionVector.X - _endpoint1.XPos) > _widthOffset && Math.Abs(positionVector.X - _endpoint2.XPos) > _widthOffset)
                {
                    Debug.WriteLineIf(_debugMessages, "positionVector is to the left");
                    return false;
                }
            }
            if (end1YLess && end2YLess)
            {
                Debug.WriteLineIf(_debugMessages, "end1YLess && end2YLess");
                // If positionVector is farther above of this Edge than _widthOffset 
                if (Math.Abs(positionVector.Y - _endpoint1.YPos) > _widthOffset && Math.Abs(positionVector.Y - _endpoint2.YPos) > _widthOffset)
                {
                    Debug.WriteLineIf(_debugMessages, "positionVector is above");
                    return false;
                }
            }
            if (end1YGreater && end2YGreater)
            {
                Debug.WriteLineIf(_debugMessages, "end1YGreater && end2YGreater");
                // If positionVector is farther below of this Edge than _widthOffset 
                if (Math.Abs(positionVector.Y - _endpoint1.YPos) > _widthOffset && Math.Abs(positionVector.Y - _endpoint2.YPos) > _widthOffset)
                {
                    Debug.WriteLineIf(_debugMessages, "positionVector is below");
                    return false;
                }
            }
            
            // Vertical Line
            if (_endpoint1.XPos == _endpoint2.XPos)
            {
                Debug.WriteLineIf(_debugMessages, "Vertical Line");
                return !((positionVector.Y < _endpoint1.YPos && positionVector.Y < _endpoint2.YPos)
                            || (positionVector.Y > _endpoint1.YPos && positionVector.Y > _endpoint2.YPos));
            }

            double distance = DistanceToPoint(positionVector);
            Debug.WriteLineIf(_debugMessages, "Endpoint1: " + _endpoint1 + " Endpoint2: " + _endpoint2 + " DistanceToPoint(" + positionVector.ToString() + ") = " + distance);

            return distance <= _widthOffset;
        }

        /// <summary>
        /// Calculates the distance from a single point as a WorldVector to an edge.
        /// </summary>
        /// <returns>The distance from the Edge to the point</returns>
        public double DistanceToPoint(WorldVector point)
        {
            double xDifference, yDifference, u;
            WorldVector pointOnLine = new WorldVector();
            WorldVector tangent;

            yDifference = _endpoint2.YPos - _endpoint1.YPos;
            xDifference = _endpoint2.XPos - _endpoint1.XPos;

            if (xDifference == 0 && yDifference == 0)
            {
                throw (new ArithmeticException("Edge endpoints are the same."));
            }

            u = ((point.X - _endpoint1.XPos) * xDifference + (point.Y - _endpoint1.YPos) * yDifference)
                    / ((xDifference * xDifference) + (yDifference * yDifference));

            // Loss of precision could be a problem
            pointOnLine.X = (float)(_endpoint1.XPos + u * xDifference);
            pointOnLine.Y = (float)(_endpoint1.YPos + u * yDifference);
            pointOnLine.Z = 0;

            tangent = new WorldVector(point, pointOnLine);

            return tangent.Magnitude;
        }

        public override string ToString()
        {
            return _name;            
        }

        #region IEquitable Implementation

        public bool Equals(Edge other)
        {
            if (other == null)
            {
                return false;
            }

            return (_endpoint1.Equals(other._endpoint1) && _endpoint2.Equals(other._endpoint2)) ||
                        (_endpoint1.Equals(other._endpoint2) && _endpoint2.Equals(other._endpoint1));
        }

        public override bool Equals(Object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (obj.GetType() != typeof(Edge))
            {
                return false;
            }

            return (_endpoint1.Equals(((Edge)obj)._endpoint1) && _endpoint2.Equals(((Edge)obj)._endpoint2)) ||
                        (_endpoint1.Equals(((Edge)obj)._endpoint2) && _endpoint2.Equals(((Edge)obj)._endpoint1));
        }

        public static bool operator ==(Edge a, Edge b)
        {
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            if (((object)a == null) || ((object)b == null))
            {
                return false;
            }

            return ((a._endpoint1 == b._endpoint1) && (a._endpoint2 == b._endpoint2)) ||
                        ((a._endpoint1 == b._endpoint2) && (a._endpoint2 == b._endpoint1));
        }

        public static bool operator !=(Edge a, Edge b)
        {
            return !(a == b);
        }

        #endregion
    }
}